package com.java2nb.novel.pay.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.util.concurrent.RateLimiter;
import com.java2nb.novel.pay.common.SignUtils;
import com.java2nb.novel.pay.entity.MchAppConfig;
import com.java2nb.novel.pay.entity.PayOrder;
import com.java2nb.novel.pay.mapper.MchAppConfigDynamicSqlSupport;
import com.java2nb.novel.pay.mapper.MchAppConfigMapper;
import com.java2nb.novel.pay.mapper.PayOrderDynamicSqlSupport;
import com.java2nb.novel.pay.mapper.PayOrderMapper;
import com.java2nb.novel.pay.service.PayOrderService;
import com.java2nb.novel.pay.vo.PayOrderRequestVO;
import com.java2nb.novel.pay.vo.PayOrderResponseVO;
import com.java2nb.novel.utils.OkHttpUtils;
import com.java2nb.novel.utils.OrderStatusEnum;
import com.java2nb.novel.utils.RedissonLockUtils;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.java2nb.novel.pay.mapper.PayOrderDynamicSqlSupport.payOrder;
import static com.java2nb.novel.utils.OrderStatusEnum.PAY_SUCCESS;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import static org.mybatis.dynamic.sql.select.SelectDSL.select;

/**
 * 支付下单业务服务
 */
@Service
@Slf4j
public class PayOrderServiceImpl implements PayOrderService {

        // 常量定义
        private static final String LOCK_KEY_PREFIX = "pay:lock:mch_order:"; // 分布式锁Key前缀
        private static final String MCH_CONFIG_CACHE_KEY_PREFIX = "pay:mch:config:"; // 商户配置缓存Key前缀
        private static final String MCH_RATE_LIMIT_KEY_PREFIX = "pay:rate_limit:mch:"; // 商户限流Key前缀
        private static final Integer PAY_STATUS_PENDING = 0; // 待支付状态
        private static final String PAY_GATEWAY_SIGN_TYPE = "MD5"; // 支付网关签名类型
        private static final String ORDER_CREATED_QUEUE = "pay.order.created.queue"; // 订单创建异步队列

        // 配置注入
        @Value("${pay.gateway.url.pay}")
        private String payGatewayUrl;
        @Value("${pay.rate-limit.global-qps}")
        private double globalQps;
        @Value("${pay.rate-limit.mch-qps}")
        private double mchQps;

        // 依赖注入
        @Autowired
        private MchAppConfigMapper mchAppConfigMapper;
        @Autowired
        private PayOrderMapper payOrderMapper;
        @Autowired
        private StringRedisTemplate redisTemplate;
        @Autowired
        private RedissonLockUtils redissonLockUtils;
        @Autowired
        private OkHttpUtils okHttpUtils;
        @Autowired
        private RabbitTemplate rabbitTemplate;

        // 全局限流器（本地）
        @SuppressWarnings("UnstableApiUsage")
        private final RateLimiter globalRateLimiter = RateLimiter.create(Math.max(1.0, globalQps));
        /**
         * 支付下单核心方法
         * @param request 下单请求
         * @return 下单响应
         */
        @Transactional(rollbackFor = Exception.class)
        public PayOrderResponseVO createOrder(PayOrderRequestVO request) {
            PayOrderResponseVO response = new PayOrderResponseVO();
            String lockKey = null;

            try {
                // ====================== 1. 全局限流 ======================
                if (!globalRateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) { // 等待100ms获取令牌，超时则限流
                    response.setCode("9999");
                    response.setMsg("系统繁忙，请稍后再试（全局限流）");
                    return signResponse(response);
                }

                // ====================== 2. 商户限流（分布式） ======================
                String mchRateLimitKey = MCH_RATE_LIMIT_KEY_PREFIX + request.getMchNo();
                // Redis限流：INCR自增，设置过期时间1s，超过mchQps则限流
                Long currentCount = redisTemplate.opsForValue().increment(mchRateLimitKey, 1);
                if (currentCount!=null && currentCount == 1) {
                    redisTemplate.expire(mchRateLimitKey, 1, TimeUnit.SECONDS); // 首次设置过期时间
                }
                if (currentCount!=null && currentCount > mchQps) {
                    response.setCode("9999");
                    response.setMsg("商户请求过于频繁，请稍后再试（商户限流）");
                    return signResponse(response);
                }

                // ====================== 3. 商户配置校验（缓存优先） ======================
                MchAppConfig mchAppConfig = getMchAppConfig(request.getMchNo(), request.getAppId());
                if (mchAppConfig == null) {
                    response.setCode("9999");
                    response.setMsg("商户配置不存在（mchNo=" + request.getMchNo() + ", appId=" + request.getAppId() + "）");
                    return signResponse(response);
                }
                // 校验商户/应用状态
                if (mchAppConfig.getMchStatus() != 1) {
                    response.setCode("9999");
                    response.setMsg("商户已禁用（mchNo=" + request.getMchNo() + "）");
                    return signResponse(response);
                }
                if (mchAppConfig.getAppStatus() != 1) {
                    response.setCode("9999");
                    response.setMsg("应用已禁用（appId=" + request.getAppId() + "）");
                    return signResponse(response);
                }
                // 校验支付方式是否支持
                if (!mchAppConfig.getSupportWayCodes().contains(request.getWayCode())) {
                    response.setCode("9999");
                    response.setMsg("不支持该支付方式（wayCode=" + request.getWayCode() + "）");
                    return signResponse(response);
                }

                // ====================== 4. 客户端签名验证 ======================
                Map<String, Object> requestMap = JSONObject.parseObject(JSON.toJSONString(request), new TypeReference<Map<String, Object>>(){});
                String keys = SignUtils.generateSign(requestMap, mchAppConfig.getSecretKey()); //根据参数和密钥生成新的加密密钥
//                boolean signValid = SignUtils.verifySign(requestMap, mchAppConfig.getSecretKey(), keys);
//                if (!signValid) {
//                    response.setCode("9999");
//                    response.setMsg("签名验证失败");
//                    return signResponse(response);
//                }

                // ====================== 5. 分布式锁（防重复下单） ======================
                lockKey = LOCK_KEY_PREFIX + request.getMchNo() + ":" + request.getMchOrderNo();
                boolean lockAcquired = redissonLockUtils.tryLock(lockKey, 500, 3000); // 等待500ms，持有3000ms
                if (!lockAcquired) {
                    response.setCode("9999");
                    response.setMsg("订单正在处理中，请稍后查询（重复下单防护）");
                    return signResponse(response);
                }

                // ====================== 6. 订单预检查（是否已存在） ======================
                PayOrder existingOrder = payOrderMapper.selectOne(
                        select(PayOrderDynamicSqlSupport.mchNo,
                                PayOrderDynamicSqlSupport.mchOrderNo,
                                PayOrderDynamicSqlSupport.orderState,
                                PayOrderDynamicSqlSupport.payOrderNo,
                                PayOrderDynamicSqlSupport.wayCode,  // 替换existingOrder.getWayCode()
                                PayOrderDynamicSqlSupport.amount,   // 替换existingOrder.getAmount()
                                PayOrderDynamicSqlSupport.expiredTime,
                                PayOrderDynamicSqlSupport.payDataType  // 补充data.setPayDataType所需字段
                        )
                        .from(payOrder)
                        .where(PayOrderDynamicSqlSupport.mchNo, isEqualTo(request.getMchNo() )  )
                        .and(PayOrderDynamicSqlSupport.mchOrderNo, isEqualTo(request.getMchOrderNo()))
                        .build()
                        .render(RenderingStrategies.MYBATIS3)
                );
                if (existingOrder != null &&
                        OrderStatusEnum.PAY_SUCCESS.getCode().intValue()==existingOrder.getOrderState().intValue()) {
                    // 已存在订单，返回当前状态
                    response.setCode("0");
                    response.setMsg("success");
                    PayOrderResponseVO.PayOrderData data = new PayOrderResponseVO.PayOrderData();
                    data.setMchOrderNo(existingOrder.getMchOrderNo()); //商户订单号
                    data.setPayOrderNo(existingOrder.getPayOrderNo()); //支付订单号
                    data.setOrderState(existingOrder.getOrderState()); //支付状态
                    data.setPayDataType(existingOrder.getPayDataType());//支付参数类型
                    data.setPayData(JSON.toJSONString(request));//支付参数
                    data.setWayCode(existingOrder.getWayCode());
                    data.setAmount(existingOrder.getAmount());
                    response.setData(data);
                    return signResponse(response, mchAppConfig.getSecretKey());
                }

                // ====================== 7. 构建支付网关请求参数 ======================
                Map<String, Object> gatewayParams = buildGatewayParams(request, mchAppConfig);
                String gatewayJsonParams = JSON.toJSONString(gatewayParams);
                log.info("调用支付网关请求参数：{}", gatewayJsonParams);

                // ====================== 8. 调用支付网关下单接口 ======================
                String gatewayResponseStr = okHttpUtils.postJson(payGatewayUrl, gatewayJsonParams);
                log.info("支付网关响应：{}", gatewayResponseStr);
                JSONObject gatewayResponseJson = JSONObject.parseObject(gatewayResponseStr);

                // ====================== 9. 支付网关响应签名验证 ======================
                Map<String, Object> gatewayResponseMap = SignUtils.jsonToMap(gatewayResponseJson);
                String gatewaySign = gatewayResponseJson.getString("sign");
                boolean gatewaySignValid = SignUtils.verifySign(gatewayResponseMap, mchAppConfig.getSecretKey(), gatewaySign);
                if (!gatewaySignValid) {
                    throw new RuntimeException("支付网关响应签名验证失败，响应：" + gatewayResponseStr);
                }

                // ====================== 10. 处理支付网关响应 ======================
                if (!"0".equals(gatewayResponseJson.getString("code"))) {
                    throw new RuntimeException("支付网关下单失败：" + gatewayResponseJson.getString("msg"));
                }

                // 解析支付网关业务数据
                JSONObject gatewayDataJson = gatewayResponseJson.getJSONObject("data");
                String payOrderNo = gatewayDataJson.getString("payOrderNo");
                String payData = gatewayDataJson.getString("payData");
                Long expiredTime = gatewayDataJson.getLong("expiredTime");

                // ====================== 11. 订单入库 ======================
                PayOrder payOrder = buildPayOrder(request, payOrderNo, payData, expiredTime);
                int insertCount = payOrderMapper.insert(payOrder);
                if (insertCount != 1) {
                    throw new RuntimeException("订单入库失败，mchOrderNo=" + request.getMchOrderNo());
                }

                // ====================== 12. 异步发送订单创建消息 ======================
                rabbitTemplate.convertAndSend(ORDER_CREATED_QUEUE, JSON.toJSONString(payOrder));
                log.info("异步发送订单创建消息，payOrderNo={}", payOrderNo);

                // ====================== 13. 构建返回结果 ======================
                response.setCode("0");
                response.setMsg("success");
                PayOrderResponseVO.PayOrderData responseData = new PayOrderResponseVO.PayOrderData();
                responseData.setMchOrderNo(request.getMchOrderNo());
                responseData.setPayOrderNo(payOrderNo);
                responseData.setWayCode(request.getWayCode());
                responseData.setAmount(request.getAmount());
                responseData.setOrderState(OrderStatusEnum.PAY_PENDING.getCode());
                responseData.setExpiredTime(expiredTime);
                responseData.setPayData(payData);
                response.setData(responseData);

            } catch (Exception e) {
                log.error("支付下单失败，request={}", JSON.toJSONString(request), e);
                response.setCode("9999");
                response.setMsg("支付下单失败：" + e.getMessage());
            } finally {
                // 释放分布式锁
                if (lockKey != null) {
                    redissonLockUtils.unlock(lockKey);
                }
            }

            // 签名响应结果
            return signResponse(response, getMchAppConfig(request.getMchNo(), request.getAppId()).getSecretKey());
        }

        /**
         * 获取商户应用配置（缓存优先，缓存失效则查DB并更新缓存）
         * @param mchNo 商户号
         * @param appId 应用ID
         * @return 商户应用配置
         */
        private MchAppConfig getMchAppConfig(String mchNo, String appId) {
            String cacheKey = MCH_CONFIG_CACHE_KEY_PREFIX + mchNo + ":" + appId;
            // 1. 查缓存
            String configJson = redisTemplate.opsForValue().get(cacheKey);
            if (configJson != null && !"".equals(configJson)) {
                return JSONObject.parseObject(configJson, MchAppConfig.class);
            }

            // 2. 缓存失效，查DB
            MchAppConfig mchAppConfig = mchAppConfigMapper.selectOne(
                    select(MchAppConfigDynamicSqlSupport.mchNo, MchAppConfigDynamicSqlSupport.appId,
                            MchAppConfigDynamicSqlSupport.secretKey, MchAppConfigDynamicSqlSupport.mchStatus,
                            MchAppConfigDynamicSqlSupport.appStatus,MchAppConfigDynamicSqlSupport.supportWayCodes
                            // 其他需要的字段
                    )
                    .from(MchAppConfigDynamicSqlSupport.mchAppConfig)
                    .where(MchAppConfigDynamicSqlSupport.mchNo, isEqualTo(mchNo))
                    .and(MchAppConfigDynamicSqlSupport.appId, isEqualTo(appId))
                    .build()
                    .render(RenderingStrategies.MYBATIS3)
            );

            // 3. 更新缓存（过期时间1h）
            if (mchAppConfig != null) {
                redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(mchAppConfig), 1, TimeUnit.HOURS);
            }

            return mchAppConfig;
        }

        /**
         * 构建支付网关请求参数（含签名）
         * @param request 下单请求
         * @param mchAppConfig 商户配置
         * @return 支付网关请求参数Map
         */
        private Map<String, Object> buildGatewayParams(PayOrderRequestVO request, MchAppConfig mchAppConfig) {
            Map<String, Object> params = new HashMap<>();
            // 必选参数
            params.put("mchNo", request.getMchNo());
            params.put("appId", request.getAppId());
            params.put("mchOrderNo", request.getMchOrderNo());
            params.put("wayCode", request.getWayCode());
            params.put("amount", request.getAmount());
            params.put("currency", request.getCurrency());
            params.put("subject", request.getSubject());
            params.put("body", request.getBody());
            // 可选参数
            if (request.getClientIp() != null) {
                params.put("clientIp", request.getClientIp());
            }
            if (request.getNotifyUrl() != null) {
                params.put("notifyUrl", request.getNotifyUrl());
            }
            if (request.getReturnUrl() != null) {
                params.put("returnUrl", request.getReturnUrl());
            }
            if (request.getExpiredTime() != null) {
                params.put("expiredTime", request.getExpiredTime());
            }
            if (request.getChannelExtra() != null) {
                params.put("channelExtra", request.getChannelExtra());
            }
            // 固定参数
            params.put("signType", PAY_GATEWAY_SIGN_TYPE);
            params.put("version", "1.0");
            params.put("reqTime", System.currentTimeMillis() / 1000); // 请求时间（秒级时间戳）

            // 生成签名
            String sign = SignUtils.generateSign(params, mchAppConfig.getSecretKey());
            params.put("sign", sign);

            return params;
        }

        /**
         * 构建支付订单实体
         * @param request 下单请求
         * @param payOrderNo 支付中心订单号
         * @param payParams 支付参数
         * @param expiredTime 失效时间（毫秒时间戳）
         * @return 支付订单实体
         */
        private PayOrder buildPayOrder(PayOrderRequestVO request, String payOrderNo, String payParams, Long expiredTime) {
            PayOrder payOrder = new PayOrder();
            payOrder.setMchNo(request.getMchNo());
            payOrder.setAppId(request.getAppId());
            payOrder.setMchOrderNo(request.getMchOrderNo());
            payOrder.setPayOrderNo(payOrderNo);
            payOrder.setWayCode(request.getWayCode());
            payOrder.setAmount(request.getAmount());
            payOrder.setCurrency(request.getCurrency());
            payOrder.setSubject(request.getSubject());
            payOrder.setBody(request.getBody());
            payOrder.setNotifyUrl(request.getNotifyUrl());
            payOrder.setReturnUrl(request.getReturnUrl());
            payOrder.setClientIp(request.getClientIp());
            payOrder.setChannelExtra(request.getChannelExtra());
            payOrder.setOrderState(PAY_STATUS_PENDING);
            payOrder.setExpiredTime( expiredTime );
            payOrder.setPayParams(payParams);
            payOrder.setCreateTime(new java.util.Date());
            payOrder.setUpdateTime(new java.util.Date());
            return payOrder;
        }

        /**
         * 响应结果签名（默认使用全局私钥，实际应使用商户私钥）
         * @param response 响应实体
         * @return 签名后的响应
         */
        private PayOrderResponseVO signResponse(PayOrderResponseVO response) {
            // 此处为默认实现，实际应传入商户私钥，或从上下文获取
            return signResponse(response, "default_secret_key");
        }

        /**
         * 响应结果签名（使用指定私钥）
         * @param response 响应实体
         * @param secretKey 私钥
         * @return 签名后的响应
         */
        private PayOrderResponseVO signResponse(PayOrderResponseVO response, String secretKey) {
            // 过滤null字段，排除sign字段
            Map<String, Object> responseMap = new HashMap<>();
            if (response.getCode() != null) {
                responseMap.put("code", response.getCode());
            }
            if (response.getMsg() != null) {
                responseMap.put("msg", response.getMsg());
            }
            if (response.getData() != null) {
                responseMap.put("data", response.getData());
            }
            responseMap.put("signType", response.getSignType());

            // 生成签名
            String sign = SignUtils.generateSign(responseMap, secretKey);
            response.setSign(sign);

            return response;
        }
    }

